package com.mysoft.aiot.plugin.feign.git;

import com.google.common.base.Strings;
import com.mysoft.aiot.plugin.feign.Environment;
import com.mysoft.aiot.plugin.feign.model.FeignText;
import com.mysoft.aiot.plugin.feign.util.Crypto;
import org.eclipse.jgit.api.*;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class GitRepository {
    private String repository;
    private String module;
    private Git git;
    private File workingDir;

    public GitRepository(String repository, String module) {
        this.repository = repository;
        this.module = module;
    }

    private String getSubPath() {
        if (Strings.isNullOrEmpty(this.module)) {
            return "src/main/java";
        } else {
            return this.module + "/src/main/java";
        }
    }

    private GitRepository initRepository() throws GitAPIException, IOException {
        workingDir = new File(System.getProperty("user.home") + "/.feign-repository/" + Crypto.md5(this.repository));
        TransportConfigCallback transportConfigCallback = new SshTransportConfigCallback();
        if (workingDir.exists()) {
            git = Git.open(workingDir);
            Environment.log.info("Git: pull " + workingDir.getAbsolutePath());
            PullResult pullResult = git.pull()
                    .setTransportConfigCallback(transportConfigCallback)
                    .call();
        } else {
            Environment.log.info("Git: clone into " + workingDir.getAbsolutePath());
            git = Git.cloneRepository()
                    .setDirectory(workingDir)
                    .setTransportConfigCallback(transportConfigCallback)
                    .setURI(this.repository)
                    .call();
        }

        return this;
    }

    public static String getCurrentBranch(String dir) throws IOException {
        Git git = Git.open(new File(dir));
        return git.getRepository().getBranch();
    }

    private void checkoutBranch(String branchName) throws GitAPIException {
        boolean existRemoteBranch = false;
        //列出所有的分支名称，判断分支是否存在
        List<Ref> branchList = git.branchList().setListMode(ListBranchCommand.ListMode.ALL).call();
        for (Ref ref : branchList) {
            if (("refs/remotes/origin/" + branchName).equals(ref.getName())) {
                existRemoteBranch = true;
                break;
            }
        }

        boolean existLocalBranch = false;
        for (Ref ref : branchList) {
            if (ref.getName().equals("refs/heads/" + branchName)) {
                existLocalBranch = true;
            }
        }

        if (existLocalBranch) {
            // 存在则切换本地分支
            git.checkout().setName(branchName).call();
        } else if (existRemoteBranch) {
            // 不存在则切换为远程分支
            git.checkout().setCreateBranch(true).setName(branchName)
                    .setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK)
                    .setStartPoint("origin/" + branchName).call();
        } else {
            // 远程分支不存在则创建新分支
            Ref ref = git.branchCreate().setName(branchName)
                    .setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK)
                    .setStartPoint("origin/master").call();
            git.push().add(ref).call();
            git.checkout().setName(branchName).call();
        }
    }

    private void addCode(List<FeignText> pathAndFileContents) throws IOException, GitAPIException {
        File targetRootPath = new File(workingDir, this.getSubPath());

        for (FeignText text : pathAndFileContents) {
            File file = new File(targetRootPath, text.getFileName());
            Environment.log.info("Generating " + text.getClazz() + " to " + file.getAbsolutePath());
            File dir = new File(file.getParent());
            if (!dir.exists()) {
                dir.mkdirs();
            }

            if (!file.exists()) {
                file.createNewFile();
            }

            try (FileWriter fw = new FileWriter(file.getAbsoluteFile());
                 BufferedWriter bw = new BufferedWriter(fw)) {
                bw.write(text.getText());
            }
        }

        List<String> relativeFiles = pathAndFileContents.stream().map(text -> this.getSubPath() + "/" + text.getFileName()).collect(Collectors.toList());
        List<DiffEntry> diffEntries = git.diff()
                .setPathFilter(PathFilterGroup.createFromStrings(relativeFiles))
                .setShowNameAndStatusOnly(true).call();
        if (diffEntries == null || diffEntries.size() == 0) {
            // 生成的文件内容都没有被修改
            Environment.log.info("Git: Nothing changed");
            return;
        }

        git.pull().call();
        //被修改过的文件
        List<String> updateFiles = new ArrayList<>();
        for (DiffEntry entry : diffEntries) {
            DiffEntry.ChangeType changeType = entry.getChangeType();
            switch (changeType) {
                case ADD:
                case COPY:
                case RENAME:
                    updateFiles.add(entry.getNewPath());
                    Environment.log.info("Git: " + changeType + " file " + entry.getNewPath());
                    break;
                case DELETE:
                case MODIFY:
                    updateFiles.add(entry.getOldPath());
                    Environment.log.info("Git: " + changeType + " file " + entry.getOldPath() + ", " + new File(entry.getNewPath()).getName());
                    break;
            }
        }

        AddCommand addCommand = git.add();
        for (String file : updateFiles) {
            addCommand.addFilepattern(file);
        }
        addCommand.call();

        // 执行 git 提交
        List<String> commitNames = updateFiles.stream().limit(3).map(x -> new File(x).getName()).collect(Collectors.toList());
        String message = "Update Feign: " + String.join(", ", commitNames);
        if (updateFiles.size() > 3) {
            message += " ...";
        }
        git.commit().setMessage(message).call();
        git.push().call();
        Environment.log.info("Git: Push to remote repository " + this.repository + " success");
    }

    public void commit(String branchName, List<FeignText> pathAndFileContents) throws GitAPIException, IOException {
        this.initRepository();
        this.checkoutBranch(branchName);
        this.addCode(pathAndFileContents);
    }
}
